﻿using System.Collections.Concurrent;

namespace Loki.Client
{
    public class LokiQueue
    {
        #region MyRegion

        private readonly LokiClient _client;
        private readonly int _queueLimit;
        private readonly int _batchPostingLimit;
        private readonly object _lock = new();

        private readonly ConcurrentQueue<LokiLogEntity> _queue = new();
        private readonly Timer _timer;
        private readonly SemaphoreSlim _slim = new(1);
        private readonly List<LokiLogEntity> _data = [];

        #endregion

        public LokiQueue(LokiClient client, int queueLimit = 0, int periodSecond = 2, int batchPostingLimit = 1000)
        {
            _client = client;
            _queueLimit = queueLimit;
            _batchPostingLimit = batchPostingLimit;
            _timer = new Timer(_ => OnTick(), null, 0, periodSecond * 1000);

        }

        public event Action<LokiLogEntity> OnOutOfLimit;

        public event Action<List<LokiLogEntity>> OnError;

        public event Func<List<LokiLogEntity>, Task> OnErrorAsync;

        public bool AddLog(LokiLogEntity entity)
        {
            if (string.IsNullOrEmpty(entity.App))
            {
                throw new Exception("LokiLogEntity.App is null or empty.");
            }

            if (!entity.Time.HasValue)
            {
                entity.Time = DateTimeOffset.UtcNow;
            }

            if (_queueLimit > 0)
            {
                lock (_lock)
                {
                    if (_queue.Count > _queueLimit)
                    {
                        if (OnOutOfLimit != null)
                        {
                            try
                            {
                                OnOutOfLimit.Invoke(entity);
                            }
                            catch { }
                        }
                        return false;
                    }
                }
            }
            _queue.Enqueue(entity);
            return true;
        }

        private async void OnTick()
        {
            var ok = _slim.Wait(0);
            if (!ok)
            {
                return;
            }

            try
            {
                for (int i = 0; i < _batchPostingLimit; i++)
                {
                    ok = _queue.TryDequeue(out var val);
                    if (!ok)
                    {
                        break;
                    }
                    _data.Add(val);
                }

                if (_data.Count > 0)
                {
                    await _client.LogAsync(_data);
                    _data.Clear();
                }
            }
            catch
            {
                if (_data.Count > 0)
                {
                    if (OnError != null)
                    {
                        try
                        {
                            OnError.Invoke(_data);
                        }
                        catch { }

                    }
                    else if (OnErrorAsync != null)
                    {
                        try
                        {
                            await OnErrorAsync(_data);
                        }
                        catch { }
                    }
                    _data.Clear();
                }
            }
            finally
            {
                _slim.Release();
            }
        }

        public async Task StopAsync()
        {
            try
            {
                _timer.Dispose();
                _slim.Wait();
                _data.Clear();
                while (_queue.TryDequeue(out var val))
                {
                    _data.Add(val);
                }

                if (_data.Count > 0)
                {
                    await _client.LogAsync(_data);
                    _data.Clear();
                }

            }
            catch { }
            finally
            {
                _slim.Release();
                _slim.Dispose();
            }
        }
    }
}
